Useful DESeq tutorials:

Useful fGSEA tutorials:

The dataset

The experimental samples consist of 72 mice. 18 mice were WT SPF, 18 were KO SPF, 18 were WT GF, and 18 were KO GF. At every 4 hours, starting from ZT2, 3 mice from each group were sac’d and had liver RNA content extracted. In this dataset are all the mice that were sac’d in the first timepoint, i.e. 3 WTSPF, 3 KOSPF, etc.

This data was generated by first running STAR alignment on the Midway server on the sequencing results. A featurecounts table was generated, which was filtered using a custom python script to separate data by timepoint.

Differential analysis

DESeq2 library setup

if (!requireNamespace("BiocManager", quietly = TRUE))
    install.packages("BiocManager")
BiocManager::install("apeglm")
BiocManager::install("DESeq2")
BiocManager::install("genefilter")
BiocManager::install("AnnotationHub")
BiocManager::install("org.Mm.eg.db")
BiocManager::install("GO.db")
BiocManager::install("biomaRt")
BiocManager::install("vsn")
install.packages(c("hexbin","feather","RColorBrewer","viridis","pheatmap"))
library(feather)
library(DESeq2)
library(dplyr)
library(magrittr)
library(genefilter)
library(AnnotationHub)
library(org.Mm.eg.db)
library(GO.db)
library(vsn)
library(pheatmap)
library(biomaRt)
library(curl)
library(RColorBrewer)
library(viridis)

Preparing for DESeq

Grab a list of all mouse protein coding genes from ensemble, read in RNA Seq data, and filter by protein coding genes.

mouseProteinCodingGenes <- useMart("ensembl") %>%
  useDataset("mmusculus_gene_ensembl",mart=.) %>%
  getBM(attributes=c("ensembl_gene_id","external_gene_name","description"), filters='biotype', values=c('protein_coding'), mart=.)
df <- read_feather("time_1_2.feather") %>%
  .[rowSums(sapply(., '%in%', mouseProteinCodingGenes$ensembl_gene_id)) > 0,]
df <- set_rownames(df, df$Geneid) %>% dplyr::select(-Geneid)

DESeq requires a leveled dataframe containing the metadata. This dataframe must be in the same order as the samples in the featurecounts data. DESeq will use this dataframe to generate the analysis.

For this dataset, this is what the condition list looked like:

Genotype Condition
Sample1 WT SPF
Sample2 WT SPF
Sample3 WT SPF
Sample4 KO SPF
Sample5 KO SPF
Sample5 KO SPF
Sample6 WT GF
Sample7 WT GF
Sample8 WT GF
Sample9 KO GF
Sample10 KO GF
Sample11 KO GF
gt_list <- rep(c(rep("WT",3), rep("KO",3)),2)
cond_list <- c(rep("SPF",6),rep("GF",6))
condition_list <- data.frame(row.names=colnames(df), Genotype=gt_list, Condition=cond_list)
condition_list$Genotype <- relevel(condition_list$Genotype, "WT")
condition_list$Condition <- relevel(condition_list$Condition, "SPF")

Generate DESeqDataSet (dds) object from the data and metadata

dds <- DESeq2::DESeqDataSetFromMatrix(countData=df,
                                      colData = condition_list,
                                      design = ~ Genotype + Condition + Genotype:Condition)

Take a look at the dds object

head(dds)
class: DESeqDataSet 
dim: 6 12 
metadata(1): version
assays(1): counts
rownames(6): ENSMUSG00000051951 ENSMUSG00000025902 ...
  ENSMUSG00000033813 ENSMUSG00000002459
rowData names(0):
colnames(12): KF_01 KF_02 ... KF_56 KF_57
colData names(2): Genotype Condition

We can visualize the dds object by variance stabilizing transformations (vst) and normalized transformed data (ntd), which we’ll later use to generate heatmaps and PCAs.

First, vsd

vsd <- vst(dds, blind = FALSE)
head(assay(vsd), 3)
                       KF_01     KF_02     KF_03     KF_19     KF_20
ENSMUSG00000051951  5.586513  5.586513  5.586513  5.777269  5.864151
ENSMUSG00000025902  6.648029  6.628288  6.679868  6.732869  6.339456
ENSMUSG00000033845 10.101805 10.138352 10.133246 10.064316 10.390333
                       KF_21     KF_37     KF_38     KF_39     KF_55
ENSMUSG00000051951  5.857618  5.586513  5.586513  5.586513  5.586513
ENSMUSG00000025902  6.873506  6.462250  6.277894  6.435737  6.185492
ENSMUSG00000033845 10.283528 10.641011 10.448340 10.612897 10.698524
                       KF_56     KF_57
ENSMUSG00000051951  5.586513  5.586513
ENSMUSG00000025902  6.314729  6.181333
ENSMUSG00000033845 10.434089 10.500890

sizeFactors for samples from vsd. A sizeFactor is a normalization value, count values/sizeFactor yields a common scale.

colData(vsd)
DataFrame with 12 rows and 3 columns
      Genotype Condition        sizeFactor
      <factor>  <factor>         <numeric>
KF_01       WT       SPF  1.06581143642804
KF_02       WT       SPF  1.10841342430678
KF_03       WT       SPF  1.00191112543556
KF_19       KO       SPF  1.18865228958857
KF_20       KO       SPF  1.12039876331493
...        ...       ...               ...
KF_38       WT        GF 0.977919662514898
KF_39       WT        GF 0.875355342841283
KF_55       KO        GF  0.95209046510223
KF_56       KO        GF 0.959622775650204
KF_57       KO        GF 0.965642851429522

Standard deviation plotted against rank means. A horizontal line indicates there is no dependence of standard deviation to the means, which is ideal.

meanSdPlot(assay(vsd))

And the same for ntd

ntd <- normTransform(dds)
meanSdPlot(assay(ntd))

vsd yields better results based off the meanSdPlot.

We can check a PCA of the transformed data

plotPCA(vsd, intgroup=c("Genotype","Condition"))

Gratifyingly, PCA agnostically separates out all four experimental groups, validating that they are indeed 4 different and discrete groups, with transcriptomic variation.

At this point, normalizing looks fine and the data looks fine, so deseq can be run.

dds is normalized using estimateSizeFactors

dds <- estimateSizeFactors(dds)
colData(dds)
DataFrame with 12 rows and 3 columns
      Genotype Condition        sizeFactor
      <factor>  <factor>         <numeric>
KF_01       WT       SPF  1.06581143642804
KF_02       WT       SPF  1.10841342430678
KF_03       WT       SPF  1.00191112543556
KF_19       KO       SPF  1.18865228958857
KF_20       KO       SPF  1.12039876331493
...        ...       ...               ...
KF_38       WT        GF 0.977919662514898
KF_39       WT        GF 0.875355342841283
KF_55       KO        GF  0.95209046510223
KF_56       KO        GF 0.959622775650204
KF_57       KO        GF 0.965642851429522

I create a “Group” column that contains both Genotype vs Condition in one column

dds$Group <- factor(paste0(dds$Genotype, dds$Condition))
dds$Group <- relevel(dds$Group, "WTSPF")
design(dds) <- ~  Group

Run DESeq

dds <- DESeq(dds)
using pre-existing size factors
estimating dispersions
gene-wise dispersion estimates
mean-dispersion relationship
final dispersion estimates
fitting model and testing

DESeq results

I’ll compare all contrasts groups using DESeq’s results function, explicitly telling it to compare items based on the dds$group column, followed by the ‘control’ vs ‘experimental’ group in that comparison. In this experimental setup, it is slightly confusing since there are 4 groups being compared. In a typical treated vs untreated experiment, there would be no need to look at all the different types of comparisons made here.

#Compare SPF v GF in WT
WT_res <- results(dds,contrast=c("Group", "WTSPF", "WTGF"), tidy = TRUE)
#Compare SPF v GF in KO
KO_res <- results(dds, contrast=c("Group", "KOSPF", "KOGF"), tidy = TRUE)
#Compare WT v KO in SPF Conditions
SPF_res <- results(dds, contrast=c("Group", "WTSPF", "KOSPF"), tidy = TRUE)
#Compare WT v KO in GF Conditions
GF_res <- results(dds, contrast=c("Group", "WTGF", "KOGF"), tidy = TRUE)
summary(WT_res)
     row               baseMean         log2FoldChange        lfcSE       
 Length:17677       Min.   :      0.0   Min.   :-8.9255   Min.   :0.0712  
 Class :character   1st Qu.:      2.9   1st Qu.:-0.2237   1st Qu.:0.1532  
 Mode  :character   Median :     96.8   Median : 0.0242   Median :0.2356  
                    Mean   :   1088.0   Mean   : 0.0983   Mean   :0.8129  
                    3rd Qu.:    493.8   3rd Qu.: 0.3746   3rd Qu.:0.7417  
                    Max.   :1261675.8   Max.   : 4.9813   Max.   :4.4074  
                                        NA's   :1283      NA's   :1283    
      stat              pvalue            padj      
 Min.   :-15.4830   Min.   :0.0000   Min.   :0.000  
 1st Qu.: -0.8081   1st Qu.:0.0799   1st Qu.:0.170  
 Median :  0.0966   Median :0.3670   Median :0.499  
 Mean   : -0.0024   Mean   :0.4122   Mean   :0.487  
 3rd Qu.:  0.9702   3rd Qu.:0.7175   3rd Qu.:0.795  
 Max.   : 11.6147   Max.   :1.0000   Max.   :1.000  
 NA's   :1283       NA's   :1286     NA's   :5084   

We can check the number of significant results and false discovery rates (multiple hypothesis corrections)

print(cat(sum(WT_res$pvalue < 0.05, na.rm=TRUE),     # How many results had p val < 0.05
          sum(!is.na(WT_res$pvalue)),
          sum(WT_res$padj < 0.1, na.rm=TRUE),        # How many results had a p adj val < 0.1
          "\n"))
3441 16391 2413 
NULL
print(cat(sum(KO_res$pvalue < 0.05, na.rm=TRUE),
          sum(!is.na(KO_res$pvalue)),
          sum(KO_res$padj < 0.1, na.rm=TRUE),
          "\n"))
3464 16391 2409 
NULL
print(cat(sum(SPF_res$pvalue < 0.05, na.rm=TRUE),
          sum(!is.na(SPF_res$pvalue)),
          sum(SPF_res$padj < 0.1, na.rm=TRUE),
          "\n"))
2671 16391 1576 
NULL
print(cat(sum(GF_res$pvalue < 0.05, na.rm=TRUE),
          sum(!is.na(GF_res$pvalue)),
          sum(GF_res$padj < 0.1, na.rm=TRUE),
          "\n"))
1938 16391 955 
NULL

LFC visualization

We can check shrunken log fold change (lfc) and check distribution of said lfc using lfcShrink. Possible values for coef can be seen using resultsNames(dds). lfc requires a comparison between 2 groups, for obvious reasons, so only 2 groups can be compared at a time.

condition_LFC <- lfcShrink(dds, coef = "Group_KOSPF_vs_WTSPF", type = 'apeglm')
using 'apeglm' for LFC shrinkage. If used in published research, please cite:
    Zhu, A., Ibrahim, J.G., Love, M.I. (2018) Heavy-tailed prior distributions for
    sequence count data: removing the noise and preserving large differences.
    Bioinformatics. https://doi.org/10.1093/bioinformatics/bty895

A MA-plot can be generated. This is a plot of log2foldchange (determined by lfcShrink) and mean of normalized counts.

p <- DESeq2::plotMA(condition_LFC, ylim=c(-2,2))

Heatmaps

Fold change can be visualized using some heatmaps. ensemble IDs are converted to gene names using the mouseProteinCodingGenes table generated from biomaRt earlier. mat is the dataframe used to generate the heatmap, and anno is used for pheatmap’s annotation

number_of_genes <- 50    # change this to the number of genes you want to be generated in the heatmap
topVarGenes <- head(order(rowVars(assay(vsd)), decreasing = TRUE), number_of_genes)
mat  <- assay(vsd)[ topVarGenes, ]
mat  <- mat - rowMeans(mat)
geneNames <- data.frame(
  row.names(mat)[match(mouseProteinCodingGenes$ensembl_gene_id, row.names(mat))],
  mouseProteinCodingGenes$external_gene_name) %>%
  na.omit() %>%
  set_colnames(c('Geneid','external_gene_name')) %>%
  .[match(row.names(mat),.$Geneid),]
mat <- set_rownames(mat,make.names(geneNames$external_gene_name, unique = T))
anno <- as.data.frame(colData(vsd)[, c("Genotype","Condition")])

Inferno theme heatmap with unclustered columns

# Inferno theme heatmap with unclustered columns
p1 <- pheatmap(mat, color = inferno(length(mat) - 1), annotation_col = anno, cluster_cols = F)
p1

Classic theme heatmap, columns and rows are clustered via pheatmap

# Classic theme heatmap, columns and rows are clustered via pheatmap
p2 <- pheatmap(mat, annotation_col = anno)
p2

Inferno theme heatmap with clustered rows and columns via pheatmap

# Inferno theme heatmap with clustered rows and columns via pheatmap
p3 <- pheatmap(mat, color = inferno(length(mat) - 1), annotation_col = anno)
p3

This is a heatmap of sample-to-sample distance. It shows how similar genotype-condition samples are to each other based on transformed data.

sampleDists <- dist(t(assay(vsd)))
sampleDistMatrix <- as.matrix(sampleDists)
rownames(sampleDistMatrix) <- paste(vsd$Genotype, vsd$Condition, sep="-")
colnames(sampleDistMatrix) <- paste(vsd$Genotype, vsd$Condition, sep="-")
colors <- colorRampPalette( rev(brewer.pal(9, "Blues")) )(255)
pheatmap(sampleDistMatrix,
         clustering_distance_rows=sampleDists,
         clustering_distance_cols=sampleDists,
         col=colors)

Pathway analysis

GSEA works by having a method to rank genes in a list of genes. Some method of ranking must be used, or GSEA will not know how to decide if a pathway is enriched or not. We’ll use the Wald statistic (a ratio of LFC to p val) as a hypothesis-generating ranking measurement, although other statistics from DESeq can be used (e.g. LFC or p adj alone), depending on the desired outcome of pathway analysis.

fGSEA library setup

BiocManager::install("fgsea")
install.packages(c("tidyverse","ggpubr"))
knitr::opts_chunk$set(warning = FALSE, message = FALSE)
library(fgsea)
library(tidyverse)
library(ggpubr)
theme_set(theme_pubr())

Getting pathways

Genesets are essentially data structures that contain some grouping of genes, e.g. pathways or locations, and a list of genes for that group. Pathways are downloaded from gsea-msigdb. For this notebook, I put them on gdrive to url request, but otherwise the pathways should be downloaded from there. The file name will include how the genes are identified - here we will use ‘symbols,’ since STAR alignment gave us ensembl IDs.

download.file("https://accounts.google.com/ServiceLogin?hl=en&passive=true&continue=https://drive.google.com/uc%3Fexport%3Ddownload%26id%3D133tI8ocIh2q0MsW51jerDePEnDH71Gcs&service=writely",
              destfile = "c2.cp.kegg.v7.0.symbols.gmt")
download.file("https://accounts.google.com/ServiceLogin?hl=en&passive=true&continue=https://drive.google.com/uc%3Fexport%3Ddownload%26id%3D1WpWISZeUUjRI6BlUKmUcJummzTxxTE8y&service=writely",
              destfile = "c5.all.v7.0.symbols.gmt")
download.file("https://drive.google.com/uc?export=download&id=1NfI1jBJkNROCDwdXe92nr3d7cffUw-fy",
              destfile = "c5.bp.v7.0.symbols.gmt")
download.file("https://drive.google.com/uc?export=download&id=1WydckDxDgehnahCwHsqKhqUAdHCqRE4h",
              destfile = "h.all.v7.0.symbols.gmt")
fgsea_output <- list()    # This will become a datastructure that will hold multiple fGSEA results
fgsea_output_plots <- list()
bm <- getBM(attributes=c("ensembl_gene_id", "hsapiens_homolog_associated_gene_name"), mart=ensemblMouse) %>%
  distinct() %>%
  as_tibble() %>%
  na_if("") %>%
  na.omit()

Run fGSEA

We’ll compare pathways in the same way we generated our differential analysis comparisons, e.g. the 4-way analysis we ran previously. In other words, we can pull the Wald statistic from the 4 results we recieved from DESeq to generate ranked lists for different pathway analysis comparisons.

We also need to consider the pathway genesets we use. For WT SPF, I’ll run pathway analysis using Hallmark, KEGG, Gene Ontology with all tags, and Gene Ontology with only Biological Processess tags.

For the sake of the tutorial, I’ll only run all databases for WT_res. I’ll run KEGG and GO for KO_res, then compare results for WT and KO to determine what pathways are being expressed depending on the genotype that vary by microbial status, or are independent of microbial status.

First I need to generate the ranked list. I build res2, a ranked gene list, based on WT_res. I will use this to run fGSEA using each of the different pathways.

res2 <- inner_join(WT_res, bm, by=c("row"="ensembl_gene_id")) %>%
  dplyr::select(hsapiens_homolog_associated_gene_name, stat) %>%
  na.omit() %>%
  distinct() %>%
  group_by(hsapiens_homolog_associated_gene_name) %>%
  summarize(stat=mean(stat))

Take a brief look at the ranks generated from WT_res

ranks <- deframe(res2)
head(ranks, 20)
       A1BG        A1CF         A2M     A3GALT2      A4GALT       A4GNT        AAAS 
-0.45147358  1.03819985  1.36453849  0.19675448  1.05196147 -0.28252894  0.51368498 
       AACS       AADAC       AADAT       AAGAB        AAK1       AAMDC        AAMP 
 0.10147056 -3.04044117 -3.57487140 -3.79072943  2.78468647 -2.25750312 -0.97911398 
      AANAT        AAR2        AARD       AARS1       AARS2      AARSD1 
 0.15390472 -0.77264471 -0.47761889 -0.63080907 -0.48650356  0.08451519 

I use fgsea() with nperm set to 100000 based on earlier testing

fgseaResTidy <- fgsea(pathways=gmtPathways("h.all.v7.0.symbols.gmt"), stats=ranks, nperm=10000) %>%
  as_tibble() %>%
  filter(padj < 0.05) %>%
  arrange(desc(NES))

Results can be seen in a table

fgseaResTidy %>%
  dplyr::select(-leadingEdge, -ES, -nMoreExtreme) %>%
  arrange(padj)

Finally, we can see how pathways are enriched or not enriched in the SPF condition compared to the GF condition for WT mice.

fgsea_output_plots$WT$hallmark <- ggplot(fgseaResTidy, aes(reorder(pathway, NES), NES)) +
  geom_col() +
  coord_flip() +
  labs(x="Pathway", y="Normalized Enrichment Score",
       title="WT SPF vs GF: Hallmark pathways NES from GSEA")
fgsea_output_plots$WT$hallmark

We’ll go ahead and run this pipe with the remaining pathway databases. I add all the fgsea results to a datastructure fgsea_output and plots to a datastructure fgsea_output_plots.

fgsea_output$WT$KEGG <- fgsea(pathways=gmtPathways("c2.cp.kegg.v7.0.symbols.gmt"), ranks, nperm=10000) %>%
  as_tibble() %>%
  filter(padj < 0.05) %>%
  arrange(padj)
fgsea_output_plots$WT$KEGG <- ggplot(fgsea_output$WT$KEGG, aes(reorder(pathway, NES), NES)) +
  geom_col() +
  coord_flip() +
  labs(x="Pathway", y="Normalized Enrichment Score",
       title="WT SPF vs GF: KEGG pathways NES from GSEA")
fgsea_output$WT$GO_BP <- fgsea(pathways=gmtPathways("c5.bp.v7.0.symbols.gmt"), ranks, nperm=10000) %>%
  as_tibble() %>%
  filter(padj < 0.05) %>%
  arrange(padj)
fgsea_output_plots$WT$GO_BP <- ggplot(fgsea_output$WT$GO_BP, aes(reorder(pathway, NES), NES)) +
  geom_col() +
  coord_flip() +
  labs(x="Pathway", y="Normalized Enrichment Score",
       title="WT SPF vs GF: GO biological processes pathways NES from GSEA")

Now to create a gene list using KO DESeq results

res2 <- inner_join(KO_res, bm, by=c("row"="ensembl_gene_id")) %>%
  dplyr::select(hsapiens_homolog_associated_gene_name, stat) %>%
  na.omit() %>%
  distinct() %>%
  group_by(hsapiens_homolog_associated_gene_name) %>%
  summarize(stat=mean(stat))
ranks <- deframe(res2)
head(ranks, 20)
       A1BG        A1CF         A2M     A3GALT2      A4GALT       A4GNT        AAAS 
-1.14384169  3.50054410 -0.82675569  0.62891093  0.64358223 -0.28076222 -0.52790413 
       AACS       AADAC       AADAT       AAGAB        AAK1       AAMDC        AAMP 
 1.21589930 -1.94850059 -6.49019486 -3.80727864  0.28445488 -4.75224452 -1.92405882 
      AANAT        AAR2        AARD       AARS1       AARS2      AARSD1 
 0.00000000 -1.03778726 -0.01154298 -1.72596764  0.95231562  1.10142757 

And subsequently run fGSEA

fgsea_output$KO$KEGG <- fgsea(pathways=gmtPathways("c2.cp.kegg.v7.0.symbols.gmt"), ranks, nperm=10000) %>%
  as_tibble() %>%
  filter(padj < 0.05) %>%
  arrange(padj)
fgsea_output_plots$KO$KEGG <- ggplot(fgsea_output$KO$KEGG, aes(reorder(pathway, NES), NES)) +
  geom_col() +
  coord_flip() +
  labs(x="Pathway", y="Normalized Enrichment Score",
       title="KO SPF vs GF: Kegg pathways NES from GSEA")
fgsea_output$KO$GO_BP <- fgsea(pathways=gmtPathways("c5.bp.v7.0.symbols.gmt"), ranks, nperm=10000) %>%
  as_tibble() %>%
  filter(padj < 0.05) %>%
  arrange(padj)
fgsea_output_plots$KO$GO_BP <- ggplot(fgsea_output$KO$GO_BP, aes(reorder(pathway, NES), NES)) +
  geom_col() +
  coord_flip() +
  labs(x="Pathway", y="Normalized Enrichment Score",
       title="KO SPF vs GF: GO biological processes pathways NES from GSEA")
f1 <- ggarrange(fgsea_output_plots$WT$KEGG, fgsea_output_plots$KO$KEGG,
          labels = c("WT KEGG", "KO KEGG"))
annotate_figure(
  f1,
  top = text_grob("\nWT and KO SPF vs GF KEGG pathways\n",
                  color = "red", face = "bold", size = 20)
)

A positive normalized enrichment score (NES) means more genes expressed for this pathway in SPF, and the pathway is enriched in SPF, a negative score means more genes expressed for this pathway in GF.

We can see what pathways showed up in both WT and KO analyses above. I’ll check KEGG and GO databases and show results only for pathways in both WT or KO conditions.

This method of combining results can be expanded using basic set theory. I am only doing an intersection in this excersize.

fgsea_output$WT_KO$KEGG <- inner_join(fgsea_output$WT$KEGG, fgsea_output$KO$KEGG, by = "pathway", suffix = c(".WT", ".KO"))

fgsea_output_plots$WT_KO$KEGG <- data.frame(pathway = fgsea_output$WT_KO$KEGG$pathway, "WT NES" = fgsea_output$WT_KO$KEGG$NES.WT, "KO NES" = fgsea_output$WT_KO$KEGG$NES.KO) %>%
  reshape2::melt(id="pathway") %>%
  ggplot(aes(x = reorder(pathway, -value), y = value, fill = variable)) +
    geom_col() +
    coord_flip() +
    facet_wrap(~ variable, labeller = labeller(variable = c("WT.NES" = "WT SPF vs GF", "KO.NES" = "KO SPF vs GF"))) +
    xlab("Pathway") +
    ylab("Normalized Enrichment Score") +
    ggtitle("\nSPF vs GF pathway enrichment\n") +
    theme(legend.position = "none", plot.title = element_text(size = 20, color = "red", face = "bold"))

fgsea_output_plots$WT_KO$KEGG

fgsea_output$WT_KO$GO_BP <- inner_join(fgsea_output$WT$GO_BP, fgsea_output$KO$GO_BP, by = "pathway", suffix = c(".WT", ".KO"))

fgsea_output_plots$WT_KO$GO_BP <- data.frame(pathway = fgsea_output$WT_KO$GO_BP$pathway, "WT NES" = fgsea_output$WT_KO$GO_BP$NES.WT, "KO NES" = fgsea_output$WT_KO$GO_BP$NES.KO) %>%
  reshape2::melt(id="pathway") %>%
  ggplot(aes(x = reorder(pathway, -value), y = value, fill = variable)) +
  geom_col() +
  coord_flip() +
  facet_wrap(~ variable, labeller = labeller(variable = c("WT.NES" = "WT SPF vs GF", "KO.NES" = "KO SPF vs GF"))) +
  xlab("Pathway") +
  ylab("Normalized Enrichment Score") +
  ggtitle("\nSPF vs GF GO BP pathway enrichment\n") +
  theme(legend.position = "none", plot.title = element_text(size = 20, color = "red", face = "bold"))

fgsea_output_plots$WT_KO$GO_BP

Thus, varying microbial condition between WT and L-Bmal1-KO at ZT2 did not lead to different pathway expression in the mice.

Further analysis includes looking at how varying genotype leads to differences between SPF and GF conditions at multiple timepoints.

This completes the tutorial for running differential analysis followed by pathway analysis using DESeq2 and fGSEA.

LS0tDQp0aXRsZTogIlJOQSBTZXEgZGlmZmVyZW50aWFsIGFuYWx5c2lzIGFuZCBwYXRod2F5IGFuYWx5c2lzIFIgbm90ZWJvb2siDQphdXRob3I6IFN1bWVlZCBZb3lvIE1hbnpvb3I8YnI+W0NoYW5nIExhYl0oaHR0cDovL2NoYW5nbGFiLnVjaGljYWdvLmVkdSkgYXQgdGhlIFVuaXZlcnNpdHkgb2YgQ2hpY2Fnbw0KZGF0ZTogQXByaWwgMTAsIDIwMjANCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2s6DQogICAgdGhlbWU6IHNhbmRzdG9uZQ0KICAgIGhpZ2hsaWdodDogdGV4dG1hdGUNCiAgICBjb2RlX2ZvbGRpbmc6ICJoaWRlIg0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDoNCiAgICAgICAgY29sbGFwc2VkOiB0cnVlDQogICAgdG9jX2RlcHRoOiAzDQogICAgaW5jbHVkZXM6DQogICAgICAgIGFmdGVyX2JvZHk6IGZvb3Rlci5odG1sDQotLS0NCg0KLS0tDQpVc2VmdWwgREVTZXEgdHV0b3JpYWxzOg0KDQotIFtERVNlcTIgdmlnbmV0dGVzXShodHRwOi8vYmlvY29uZHVjdG9yLm9yZy9wYWNrYWdlcy9kZXZlbC9iaW9jL3ZpZ25ldHRlcy9ERVNlcTIvaW5zdC9kb2MvREVTZXEyLmh0bWwpDQoNClVzZWZ1bCBmR1NFQSB0dXRvcmlhbHM6DQoNCi0gW0dpdEh1Yl0oaHR0cHM6Ly9naXRodWIuY29tL2N0bGFiL2Znc2VhKQ0KLSBbU3RlcGhlbiBUdXJuZXIgREVTZXEyIHRvIGZnc2VhIHR1dG9yaWFsXShodHRwczovL3N0ZXBoZW50dXJuZXIuZ2l0aHViLmlvL2Rlc2VxLXRvLWZnc2VhLykNCi0gW2Znc2VhIG1hbnVhbCwgc2VlIHBhZ2UgNV0oaHR0cHM6Ly9iaW9jb25kdWN0b3Iub3JnL3BhY2thZ2VzL2RldmVsL2Jpb2MvbWFudWFscy9mZ3NlYS9tYW4vZmdzZWEucGRmKQ0KLSBbQnJvYWQgSW5zdGl0dXRlIHdpa2kgKHNlZSBwcmVyYW5rZWQgc2VjdGlvbildKGh0dHBzOi8vc29mdHdhcmUuYnJvYWRpbnN0aXR1dGUub3JnL2NhbmNlci9zb2Z0d2FyZS9nc2VhL3dpa2kvaW5kZXgucGhwL1VzaW5nX1JOQS1zZXFfRGF0YXNldHNfd2l0aF9HU0VBKQ0KDQojIyBUaGUgZGF0YXNldA0KDQpUaGUgZXhwZXJpbWVudGFsIHNhbXBsZXMgY29uc2lzdCBvZiA3MiBtaWNlLiAxOCBtaWNlIHdlcmUgV1QgU1BGLCAxOCB3ZXJlIEtPIFNQRiwgMTggd2VyZSBXVCBHRiwgYW5kIDE4IHdlcmUgS08gR0YuIEF0IGV2ZXJ5IDQgaG91cnMsIHN0YXJ0aW5nIGZyb20gWlQyLCAzIG1pY2UgZnJvbSBlYWNoIGdyb3VwIHdlcmUgc2FjJ2QgYW5kIGhhZCBsaXZlciBSTkEgY29udGVudCBleHRyYWN0ZWQuIEluIHRoaXMgZGF0YXNldCBhcmUgYWxsIHRoZSBtaWNlIHRoYXQgd2VyZSBzYWMnZCBpbiB0aGUgZmlyc3QgdGltZXBvaW50LCBpLmUuIDMgV1RTUEYsIDMgS09TUEYsIGV0Yy4NCg0KVGhpcyBkYXRhIHdhcyBnZW5lcmF0ZWQgYnkgZmlyc3QgcnVubmluZyBTVEFSIGFsaWdubWVudCBvbiB0aGUgTWlkd2F5IHNlcnZlciBvbiB0aGUgc2VxdWVuY2luZyByZXN1bHRzLiBBIGZlYXR1cmVjb3VudHMgdGFibGUgd2FzIGdlbmVyYXRlZCwgd2hpY2ggd2FzIGZpbHRlcmVkIHVzaW5nIGEgY3VzdG9tIHB5dGhvbiBzY3JpcHQgdG8gc2VwYXJhdGUgZGF0YSBieSB0aW1lcG9pbnQuDQoNCiMjIERpZmZlcmVudGlhbCBhbmFseXNpcw0KDQojIyMgREVTZXEyIGxpYnJhcnkgc2V0dXANCg0KYGBge3IsIGV2YWw9RkFMU0V9DQppZiAoIXJlcXVpcmVOYW1lc3BhY2UoIkJpb2NNYW5hZ2VyIiwgcXVpZXRseSA9IFRSVUUpKQ0KICAgIGluc3RhbGwucGFja2FnZXMoIkJpb2NNYW5hZ2VyIikNCkJpb2NNYW5hZ2VyOjppbnN0YWxsKCJhcGVnbG0iKQ0KQmlvY01hbmFnZXI6Omluc3RhbGwoIkRFU2VxMiIpDQpCaW9jTWFuYWdlcjo6aW5zdGFsbCgiZ2VuZWZpbHRlciIpDQpCaW9jTWFuYWdlcjo6aW5zdGFsbCgiQW5ub3RhdGlvbkh1YiIpDQpCaW9jTWFuYWdlcjo6aW5zdGFsbCgib3JnLk1tLmVnLmRiIikNCkJpb2NNYW5hZ2VyOjppbnN0YWxsKCJHTy5kYiIpDQpCaW9jTWFuYWdlcjo6aW5zdGFsbCgiYmlvbWFSdCIpDQpCaW9jTWFuYWdlcjo6aW5zdGFsbCgidnNuIikNCmluc3RhbGwucGFja2FnZXMoYygiaGV4YmluIiwiZmVhdGhlciIsIlJDb2xvckJyZXdlciIsInZpcmlkaXMiLCJwaGVhdG1hcCIpKQ0KYGBgDQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KbGlicmFyeShmZWF0aGVyKQ0KbGlicmFyeShERVNlcTIpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShtYWdyaXR0cikNCmxpYnJhcnkoZ2VuZWZpbHRlcikNCmxpYnJhcnkoQW5ub3RhdGlvbkh1YikNCmxpYnJhcnkob3JnLk1tLmVnLmRiKQ0KbGlicmFyeShHTy5kYikNCmxpYnJhcnkodnNuKQ0KbGlicmFyeShwaGVhdG1hcCkNCmxpYnJhcnkoYmlvbWFSdCkNCmxpYnJhcnkoY3VybCkNCmxpYnJhcnkoUkNvbG9yQnJld2VyKQ0KbGlicmFyeSh2aXJpZGlzKQ0KYGBgDQoNCiMjIyBQcmVwYXJpbmcgZm9yIERFU2VxDQoNCkdyYWIgYSBsaXN0IG9mIGFsbCBtb3VzZSBwcm90ZWluIGNvZGluZyBnZW5lcyBmcm9tIGVuc2VtYmxlLCByZWFkIGluIFJOQSBTZXEgZGF0YSwgYW5kIGZpbHRlciBieSBwcm90ZWluIGNvZGluZyBnZW5lcy4NCg0KYGBge3J9DQptb3VzZVByb3RlaW5Db2RpbmdHZW5lcyA8LSB1c2VNYXJ0KCJlbnNlbWJsIikgJT4lDQogIHVzZURhdGFzZXQoIm1tdXNjdWx1c19nZW5lX2Vuc2VtYmwiLG1hcnQ9LikgJT4lDQogIGdldEJNKGF0dHJpYnV0ZXM9YygiZW5zZW1ibF9nZW5lX2lkIiwiZXh0ZXJuYWxfZ2VuZV9uYW1lIiwiZGVzY3JpcHRpb24iKSwgZmlsdGVycz0nYmlvdHlwZScsIHZhbHVlcz1jKCdwcm90ZWluX2NvZGluZycpLCBtYXJ0PS4pDQpgYGANCg0KYGBge3IsIGV2YWw9RkFMU0V9DQpkZiA8LSByZWFkX2ZlYXRoZXIoInRpbWVfMV8yLmZlYXRoZXIiKSAlPiUNCiAgLltyb3dTdW1zKHNhcHBseSguLCAnJWluJScsIG1vdXNlUHJvdGVpbkNvZGluZ0dlbmVzJGVuc2VtYmxfZ2VuZV9pZCkpID4gMCxdDQpkZiA8LSBzZXRfcm93bmFtZXMoZGYsIGRmJEdlbmVpZCkgJT4lIGRwbHlyOjpzZWxlY3QoLUdlbmVpZCkNCmBgYA0KDQpERVNlcSByZXF1aXJlcyBhIGxldmVsZWQgZGF0YWZyYW1lIGNvbnRhaW5pbmcgdGhlIG1ldGFkYXRhLiBUaGlzIGRhdGFmcmFtZSAqKm11c3QqKiBiZSBpbiB0aGUgc2FtZSBvcmRlciBhcyB0aGUgc2FtcGxlcyBpbiB0aGUgZmVhdHVyZWNvdW50cyBkYXRhLiBERVNlcSB3aWxsIHVzZSB0aGlzIGRhdGFmcmFtZSB0byBnZW5lcmF0ZSB0aGUgYW5hbHlzaXMuDQoNCkZvciB0aGlzIGRhdGFzZXQsIHRoaXMgaXMgd2hhdCB0aGUgY29uZGl0aW9uIGxpc3QgbG9va2VkIGxpa2U6DQoNCnwgICAgICAgICAgfCBHZW5vdHlwZSB8IENvbmRpdGlvbiB8DQp8LS0tLS0tLS0tLXwtLS0tLS0tLS0tfC0tLS0tLS0tLS0tfA0KfCBTYW1wbGUxICB8IFdUICAgICAgIHwgU1BGICAgICAgIHwNCnwgU2FtcGxlMiAgfCBXVCAgICAgICB8IFNQRiAgICAgICB8DQp8IFNhbXBsZTMgIHwgV1QgICAgICAgfCBTUEYgICAgICAgfA0KfCBTYW1wbGU0ICB8IEtPICAgICAgIHwgU1BGICAgICAgIHwNCnwgU2FtcGxlNSAgfCBLTyAgICAgICB8IFNQRiAgICAgICB8DQp8IFNhbXBsZTUgIHwgS08gICAgICAgfCBTUEYgICAgICAgfA0KfCBTYW1wbGU2ICB8IFdUICAgICAgIHwgR0YgICAgICAgIHwNCnwgU2FtcGxlNyAgfCBXVCAgICAgICB8IEdGICAgICAgICB8DQp8IFNhbXBsZTggIHwgV1QgICAgICAgfCBHRiAgICAgICAgfA0KfCBTYW1wbGU5ICB8IEtPICAgICAgIHwgR0YgICAgICAgIHwNCnwgU2FtcGxlMTAgfCBLTyAgICAgICB8IEdGICAgICAgICB8DQp8IFNhbXBsZTExIHwgS08gICAgICAgfCBHRiAgICAgICAgfA0KDQpgYGB7cn0NCmd0X2xpc3QgPC0gcmVwKGMocmVwKCJXVCIsMyksIHJlcCgiS08iLDMpKSwyKQ0KY29uZF9saXN0IDwtIGMocmVwKCJTUEYiLDYpLHJlcCgiR0YiLDYpKQ0KDQpjb25kaXRpb25fbGlzdCA8LSBkYXRhLmZyYW1lKHJvdy5uYW1lcz1jb2xuYW1lcyhkZiksIEdlbm90eXBlPWd0X2xpc3QsIENvbmRpdGlvbj1jb25kX2xpc3QpDQoNCmNvbmRpdGlvbl9saXN0JEdlbm90eXBlIDwtIHJlbGV2ZWwoY29uZGl0aW9uX2xpc3QkR2Vub3R5cGUsICJXVCIpDQpjb25kaXRpb25fbGlzdCRDb25kaXRpb24gPC0gcmVsZXZlbChjb25kaXRpb25fbGlzdCRDb25kaXRpb24sICJTUEYiKQ0KYGBgDQoNCkdlbmVyYXRlIERFU2VxRGF0YVNldCAoZGRzKSBvYmplY3QgZnJvbSB0aGUgZGF0YSBhbmQgbWV0YWRhdGENCg0KYGBge3IsIGV2YWw9RkFMU0V9DQpkZHMgPC0gREVTZXEyOjpERVNlcURhdGFTZXRGcm9tTWF0cml4KGNvdW50RGF0YT1kZiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sRGF0YSA9IGNvbmRpdGlvbl9saXN0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXNpZ24gPSB+IEdlbm90eXBlICsgQ29uZGl0aW9uICsgR2Vub3R5cGU6Q29uZGl0aW9uKQ0KYGBgDQoNClRha2UgYSBsb29rIGF0IHRoZSBkZHMgb2JqZWN0DQoNCmBgYHtyfQ0KaGVhZChkZHMpDQpgYGANCg0KV2UgY2FuIHZpc3VhbGl6ZSB0aGUgZGRzIG9iamVjdCBieSB2YXJpYW5jZSBzdGFiaWxpemluZyB0cmFuc2Zvcm1hdGlvbnMgKHZzdCkgYW5kIG5vcm1hbGl6ZWQgdHJhbnNmb3JtZWQgZGF0YSAobnRkKSwgd2hpY2ggd2UnbGwgbGF0ZXIgdXNlIHRvIGdlbmVyYXRlIGhlYXRtYXBzIGFuZCBQQ0FzLg0KDQpGaXJzdCwgdnNkDQoNCmBgYHtyfQ0KdnNkIDwtIHZzdChkZHMsIGJsaW5kID0gRkFMU0UpDQpoZWFkKGFzc2F5KHZzZCksIDMpDQpgYGANCg0Kc2l6ZUZhY3RvcnMgZm9yIHNhbXBsZXMgZnJvbSB2c2QuIEEgc2l6ZUZhY3RvciBpcyBhIG5vcm1hbGl6YXRpb24gdmFsdWUsIGNvdW50IHZhbHVlcy9zaXplRmFjdG9yIHlpZWxkcyBhIGNvbW1vbiBzY2FsZS4NCg0KYGBge3J9DQpjb2xEYXRhKHZzZCkNCmBgYA0KDQpTdGFuZGFyZCBkZXZpYXRpb24gcGxvdHRlZCBhZ2FpbnN0IHJhbmsgbWVhbnMuIEEgaG9yaXpvbnRhbCBsaW5lIGluZGljYXRlcyB0aGVyZSBpcyBubyBkZXBlbmRlbmNlIG9mIHN0YW5kYXJkIGRldmlhdGlvbiB0byB0aGUgbWVhbnMsIHdoaWNoIGlzIGlkZWFsLg0KDQpgYGB7cn0NCm1lYW5TZFBsb3QoYXNzYXkodnNkKSkNCmBgYA0KDQpBbmQgdGhlIHNhbWUgZm9yIG50ZA0KDQpgYGB7cn0NCm50ZCA8LSBub3JtVHJhbnNmb3JtKGRkcykNCm1lYW5TZFBsb3QoYXNzYXkobnRkKSkNCmBgYA0KDQp2c2QgeWllbGRzIGJldHRlciByZXN1bHRzIGJhc2VkIG9mZiB0aGUgbWVhblNkUGxvdC4NCg0KV2UgY2FuIGNoZWNrIGEgUENBIG9mIHRoZSB0cmFuc2Zvcm1lZCBkYXRhDQoNCmBgYHtyfQ0KcGxvdFBDQSh2c2QsIGludGdyb3VwPWMoIkdlbm90eXBlIiwiQ29uZGl0aW9uIikpDQpgYGANCg0KR3JhdGlmeWluZ2x5LCBQQ0EgYWdub3N0aWNhbGx5IHNlcGFyYXRlcyBvdXQgYWxsIGZvdXIgZXhwZXJpbWVudGFsIGdyb3VwcywgdmFsaWRhdGluZyB0aGF0IHRoZXkgYXJlIGluZGVlZCA0IGRpZmZlcmVudCBhbmQgZGlzY3JldGUgZ3JvdXBzLCB3aXRoIHRyYW5zY3JpcHRvbWljIHZhcmlhdGlvbi4NCg0KQXQgdGhpcyBwb2ludCwgbm9ybWFsaXppbmcgbG9va3MgZmluZSBhbmQgdGhlIGRhdGEgbG9va3MgZmluZSwgc28gZGVzZXEgY2FuIGJlIHJ1bi4NCg0KZGRzIGlzIG5vcm1hbGl6ZWQgdXNpbmcgZXN0aW1hdGVTaXplRmFjdG9ycw0KDQpgYGB7cn0NCmRkcyA8LSBlc3RpbWF0ZVNpemVGYWN0b3JzKGRkcykNCmNvbERhdGEoZGRzKQ0KYGBgDQoNCkkgY3JlYXRlIGEgIkdyb3VwIiBjb2x1bW4gdGhhdCBjb250YWlucyBib3RoIEdlbm90eXBlIHZzIENvbmRpdGlvbiBpbiBvbmUgY29sdW1uDQoNCmBgYHtyfQ0KZGRzJEdyb3VwIDwtIGZhY3RvcihwYXN0ZTAoZGRzJEdlbm90eXBlLCBkZHMkQ29uZGl0aW9uKSkNCmRkcyRHcm91cCA8LSByZWxldmVsKGRkcyRHcm91cCwgIldUU1BGIikNCmRlc2lnbihkZHMpIDwtIH4gIEdyb3VwDQpgYGANCg0KIyMjIFJ1biBERVNlcQ0KDQpgYGB7cn0NCmRkcyA8LSBERVNlcShkZHMpDQpgYGANCg0KIyMjIERFU2VxIHJlc3VsdHMNCg0KSSdsbCBjb21wYXJlIGFsbCBjb250cmFzdHMgZ3JvdXBzIHVzaW5nIERFU2VxJ3MgYHJlc3VsdHNgIGZ1bmN0aW9uLCBleHBsaWNpdGx5IHRlbGxpbmcgaXQgdG8gY29tcGFyZSBpdGVtcyBiYXNlZCBvbiB0aGUgYGRkcyRncm91cGAgY29sdW1uLCBmb2xsb3dlZCBieSB0aGUgJ2NvbnRyb2wnIHZzICdleHBlcmltZW50YWwnIGdyb3VwIGluIHRoYXQgY29tcGFyaXNvbi4gSW4gdGhpcyBleHBlcmltZW50YWwgc2V0dXAsIGl0IGlzIHNsaWdodGx5IGNvbmZ1c2luZyBzaW5jZSB0aGVyZSBhcmUgNCBncm91cHMgYmVpbmcgY29tcGFyZWQuIEluIGEgdHlwaWNhbCB0cmVhdGVkIHZzIHVudHJlYXRlZCBleHBlcmltZW50LCB0aGVyZSB3b3VsZCBiZSBubyBuZWVkIHRvIGxvb2sgYXQgYWxsIHRoZSBkaWZmZXJlbnQgdHlwZXMgb2YgY29tcGFyaXNvbnMgbWFkZSBoZXJlLg0KDQpgYGB7cn0NCiNDb21wYXJlIFNQRiB2IEdGIGluIFdUDQpXVF9yZXMgPC0gcmVzdWx0cyhkZHMsY29udHJhc3Q9YygiR3JvdXAiLCAiV1RTUEYiLCAiV1RHRiIpLCB0aWR5ID0gVFJVRSkNCiNDb21wYXJlIFNQRiB2IEdGIGluIEtPDQpLT19yZXMgPC0gcmVzdWx0cyhkZHMsIGNvbnRyYXN0PWMoIkdyb3VwIiwgIktPU1BGIiwgIktPR0YiKSwgdGlkeSA9IFRSVUUpDQojQ29tcGFyZSBXVCB2IEtPIGluIFNQRiBDb25kaXRpb25zDQpTUEZfcmVzIDwtIHJlc3VsdHMoZGRzLCBjb250cmFzdD1jKCJHcm91cCIsICJXVFNQRiIsICJLT1NQRiIpLCB0aWR5ID0gVFJVRSkNCiNDb21wYXJlIFdUIHYgS08gaW4gR0YgQ29uZGl0aW9ucw0KR0ZfcmVzIDwtIHJlc3VsdHMoZGRzLCBjb250cmFzdD1jKCJHcm91cCIsICJXVEdGIiwgIktPR0YiKSwgdGlkeSA9IFRSVUUpDQpgYGANCg0KYGBge3J9DQpzdW1tYXJ5KFdUX3JlcykNCmBgYA0KDQpXZSBjYW4gY2hlY2sgdGhlIG51bWJlciBvZiBzaWduaWZpY2FudCByZXN1bHRzIGFuZCBmYWxzZSBkaXNjb3ZlcnkgcmF0ZXMgKG11bHRpcGxlIGh5cG90aGVzaXMgY29ycmVjdGlvbnMpDQoNCmBgYHtyfQ0KcHJpbnQoY2F0KHN1bShXVF9yZXMkcHZhbHVlIDwgMC4wNSwgbmEucm09VFJVRSksICAgICAjIEhvdyBtYW55IHJlc3VsdHMgaGFkIHAgdmFsIDwgMC4wNQ0KICAgICAgICAgIHN1bSghaXMubmEoV1RfcmVzJHB2YWx1ZSkpLA0KICAgICAgICAgIHN1bShXVF9yZXMkcGFkaiA8IDAuMSwgbmEucm09VFJVRSksICAgICAgICAjIEhvdyBtYW55IHJlc3VsdHMgaGFkIGEgcCBhZGogdmFsIDwgMC4xDQogICAgICAgICAgIlxuIikpDQpgYGANCg0KYGBge3J9DQpwcmludChjYXQoc3VtKEtPX3JlcyRwdmFsdWUgPCAwLjA1LCBuYS5ybT1UUlVFKSwNCiAgICAgICAgICBzdW0oIWlzLm5hKEtPX3JlcyRwdmFsdWUpKSwNCiAgICAgICAgICBzdW0oS09fcmVzJHBhZGogPCAwLjEsIG5hLnJtPVRSVUUpLA0KICAgICAgICAgICJcbiIpKQ0KYGBgDQoNCmBgYHtyfQ0KcHJpbnQoY2F0KHN1bShTUEZfcmVzJHB2YWx1ZSA8IDAuMDUsIG5hLnJtPVRSVUUpLA0KICAgICAgICAgIHN1bSghaXMubmEoU1BGX3JlcyRwdmFsdWUpKSwNCiAgICAgICAgICBzdW0oU1BGX3JlcyRwYWRqIDwgMC4xLCBuYS5ybT1UUlVFKSwNCiAgICAgICAgICAiXG4iKSkNCmBgYA0KDQpgYGB7cn0NCnByaW50KGNhdChzdW0oR0ZfcmVzJHB2YWx1ZSA8IDAuMDUsIG5hLnJtPVRSVUUpLA0KICAgICAgICAgIHN1bSghaXMubmEoR0ZfcmVzJHB2YWx1ZSkpLA0KICAgICAgICAgIHN1bShHRl9yZXMkcGFkaiA8IDAuMSwgbmEucm09VFJVRSksDQogICAgICAgICAgIlxuIikpDQpgYGANCg0KIyMjIyBMRkMgdmlzdWFsaXphdGlvbg0KDQpXZSBjYW4gY2hlY2sgc2hydW5rZW4gbG9nIGZvbGQgY2hhbmdlIChsZmMpIGFuZCBjaGVjayBkaXN0cmlidXRpb24gb2Ygc2FpZCBsZmMgdXNpbmcgYGxmY1Nocmlua2AuIFBvc3NpYmxlIHZhbHVlcyBmb3IgYGNvZWZgIGNhbiBiZSBzZWVuIHVzaW5nIGByZXN1bHRzTmFtZXMoZGRzKWAuIGxmYyByZXF1aXJlcyBhIGNvbXBhcmlzb24gYmV0d2VlbiAyIGdyb3VwcywgZm9yIG9idmlvdXMgcmVhc29ucywgc28gb25seSAyIGdyb3VwcyBjYW4gYmUgY29tcGFyZWQgYXQgYSB0aW1lLg0KDQpgYGB7cn0NCmNvbmRpdGlvbl9MRkMgPC0gbGZjU2hyaW5rKGRkcywgY29lZiA9ICJHcm91cF9LT1NQRl92c19XVFNQRiIsIHR5cGUgPSAnYXBlZ2xtJykNCmBgYA0KDQpBIE1BLXBsb3QgY2FuIGJlIGdlbmVyYXRlZC4gVGhpcyBpcyBhIHBsb3Qgb2YgbG9nMmZvbGRjaGFuZ2UgKGRldGVybWluZWQgYnkgbGZjU2hyaW5rKSBhbmQgbWVhbiBvZiBub3JtYWxpemVkIGNvdW50cy4NCg0KYGBge3J9DQpwIDwtIERFU2VxMjo6cGxvdE1BKGNvbmRpdGlvbl9MRkMsIHlsaW09YygtMiwyKSkNCmBgYA0KDQojIyMjIEhlYXRtYXBzDQoNCkZvbGQgY2hhbmdlIGNhbiBiZSB2aXN1YWxpemVkIHVzaW5nIHNvbWUgaGVhdG1hcHMuIGVuc2VtYmxlIElEcyBhcmUgY29udmVydGVkIHRvIGdlbmUgbmFtZXMgdXNpbmcgdGhlIG1vdXNlUHJvdGVpbkNvZGluZ0dlbmVzIHRhYmxlIGdlbmVyYXRlZCBmcm9tIGJpb21hUnQgZWFybGllci4gYG1hdGAgaXMgdGhlIGRhdGFmcmFtZSB1c2VkIHRvIGdlbmVyYXRlIHRoZSBoZWF0bWFwLCBhbmQgYGFubm9gIGlzIHVzZWQgZm9yIHBoZWF0bWFwJ3MgYW5ub3RhdGlvbg0KDQpgYGB7cn0NCm51bWJlcl9vZl9nZW5lcyA8LSA1MCAgICAjIGNoYW5nZSB0aGlzIHRvIHRoZSBudW1iZXIgb2YgZ2VuZXMgeW91IHdhbnQgdG8gYmUgZ2VuZXJhdGVkIGluIHRoZSBoZWF0bWFwDQp0b3BWYXJHZW5lcyA8LSBoZWFkKG9yZGVyKHJvd1ZhcnMoYXNzYXkodnNkKSksIGRlY3JlYXNpbmcgPSBUUlVFKSwgbnVtYmVyX29mX2dlbmVzKQ0KbWF0ICA8LSBhc3NheSh2c2QpWyB0b3BWYXJHZW5lcywgXQ0KbWF0ICA8LSBtYXQgLSByb3dNZWFucyhtYXQpDQpnZW5lTmFtZXMgPC0gZGF0YS5mcmFtZSgNCiAgcm93Lm5hbWVzKG1hdClbbWF0Y2gobW91c2VQcm90ZWluQ29kaW5nR2VuZXMkZW5zZW1ibF9nZW5lX2lkLCByb3cubmFtZXMobWF0KSldLA0KICBtb3VzZVByb3RlaW5Db2RpbmdHZW5lcyRleHRlcm5hbF9nZW5lX25hbWUpICU+JQ0KICBuYS5vbWl0KCkgJT4lDQogIHNldF9jb2xuYW1lcyhjKCdHZW5laWQnLCdleHRlcm5hbF9nZW5lX25hbWUnKSkgJT4lDQogIC5bbWF0Y2gocm93Lm5hbWVzKG1hdCksLiRHZW5laWQpLF0NCm1hdCA8LSBzZXRfcm93bmFtZXMobWF0LG1ha2UubmFtZXMoZ2VuZU5hbWVzJGV4dGVybmFsX2dlbmVfbmFtZSwgdW5pcXVlID0gVCkpDQphbm5vIDwtIGFzLmRhdGEuZnJhbWUoY29sRGF0YSh2c2QpWywgYygiR2Vub3R5cGUiLCJDb25kaXRpb24iKV0pDQpgYGANCg0KSW5mZXJubyB0aGVtZSBoZWF0bWFwIHdpdGggdW5jbHVzdGVyZWQgY29sdW1ucw0KDQpgYGB7ciwgZmlnLmhlaWdodD03LCBmaWcud2lkdGg9N30NCnAxIDwtIHBoZWF0bWFwKG1hdCwgY29sb3IgPSBpbmZlcm5vKGxlbmd0aChtYXQpIC0gMSksIGFubm90YXRpb25fY29sID0gYW5ubywgY2x1c3Rlcl9jb2xzID0gRikNCnAxDQpgYGANCg0KQ2xhc3NpYyB0aGVtZSBoZWF0bWFwLCBjb2x1bW5zIGFuZCByb3dzIGFyZSBjbHVzdGVyZWQgdmlhIHBoZWF0bWFwDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTcsIGZpZy53aWR0aD03fQ0KcDIgPC0gcGhlYXRtYXAobWF0LCBhbm5vdGF0aW9uX2NvbCA9IGFubm8pDQpwMg0KYGBgDQoNCkluZmVybm8gdGhlbWUgaGVhdG1hcCB3aXRoIGNsdXN0ZXJlZCByb3dzIGFuZCBjb2x1bW5zIHZpYSBwaGVhdG1hcA0KDQpgYGB7ciwgZmlnLmhlaWdodD03LCBmaWcud2lkdGg9N30NCnAzIDwtIHBoZWF0bWFwKG1hdCwgY29sb3IgPSBpbmZlcm5vKGxlbmd0aChtYXQpIC0gMSksIGFubm90YXRpb25fY29sID0gYW5ubykNCnAzDQpgYGANCg0KVGhpcyBpcyBhIGhlYXRtYXAgb2Ygc2FtcGxlLXRvLXNhbXBsZSBkaXN0YW5jZS4gSXQgc2hvd3MgaG93IHNpbWlsYXIgZ2Vub3R5cGUtY29uZGl0aW9uIHNhbXBsZXMgYXJlIHRvIGVhY2ggb3RoZXIgYmFzZWQgb24gdHJhbnNmb3JtZWQgZGF0YS4NCg0KYGBge3J9DQpzYW1wbGVEaXN0cyA8LSBkaXN0KHQoYXNzYXkodnNkKSkpDQoNCnNhbXBsZURpc3RNYXRyaXggPC0gYXMubWF0cml4KHNhbXBsZURpc3RzKQ0Kcm93bmFtZXMoc2FtcGxlRGlzdE1hdHJpeCkgPC0gcGFzdGUodnNkJEdlbm90eXBlLCB2c2QkQ29uZGl0aW9uLCBzZXA9Ii0iKQ0KY29sbmFtZXMoc2FtcGxlRGlzdE1hdHJpeCkgPC0gcGFzdGUodnNkJEdlbm90eXBlLCB2c2QkQ29uZGl0aW9uLCBzZXA9Ii0iKQ0KY29sb3JzIDwtIGNvbG9yUmFtcFBhbGV0dGUoIHJldihicmV3ZXIucGFsKDksICJCbHVlcyIpKSApKDI1NSkNCg0KcGhlYXRtYXAoc2FtcGxlRGlzdE1hdHJpeCwNCiAgICAgICAgIGNsdXN0ZXJpbmdfZGlzdGFuY2Vfcm93cz1zYW1wbGVEaXN0cywNCiAgICAgICAgIGNsdXN0ZXJpbmdfZGlzdGFuY2VfY29scz1zYW1wbGVEaXN0cywNCiAgICAgICAgIGNvbD1jb2xvcnMpDQpgYGANCg0KIyMgUGF0aHdheSBhbmFseXNpcw0KDQpHU0VBIHdvcmtzIGJ5IGhhdmluZyBhIG1ldGhvZCB0byByYW5rIGdlbmVzIGluIGEgbGlzdCBvZiBnZW5lcy4gU29tZSBtZXRob2Qgb2YgcmFua2luZyBtdXN0IGJlIHVzZWQsIG9yIEdTRUEgd2lsbCBub3Qga25vdyBob3cgdG8gZGVjaWRlIGlmIGEgcGF0aHdheSBpcyBlbnJpY2hlZCBvciBub3QuIFdlJ2xsIHVzZSB0aGUgV2FsZCBzdGF0aXN0aWMgKGEgcmF0aW8gb2YgTEZDIHRvIHAgdmFsKSBhcyBhIGh5cG90aGVzaXMtZ2VuZXJhdGluZyByYW5raW5nIG1lYXN1cmVtZW50LCBhbHRob3VnaCBvdGhlciBzdGF0aXN0aWNzIGZyb20gREVTZXEgY2FuIGJlIHVzZWQgKGUuZy4gTEZDIG9yIHAgYWRqIGFsb25lKSwgZGVwZW5kaW5nIG9uIHRoZSBkZXNpcmVkIG91dGNvbWUgb2YgcGF0aHdheSBhbmFseXNpcy4NCg0KIyMjIGZHU0VBIGxpYnJhcnkgc2V0dXANCg0KYGBge3IsIGV2YWw9RkFMU0V9DQpCaW9jTWFuYWdlcjo6aW5zdGFsbCgiZmdzZWEiKQ0KaW5zdGFsbC5wYWNrYWdlcyhjKCJ0aWR5dmVyc2UiLCJnZ3B1YnIiKSkNCmBgYA0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShmZ3NlYSkNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShnZ3B1YnIpDQoNCnRoZW1lX3NldCh0aGVtZV9wdWJyKCkpDQpgYGANCg0KIyMjIyBHZXR0aW5nIHBhdGh3YXlzDQoNCkdlbmVzZXRzIGFyZSBlc3NlbnRpYWxseSBkYXRhIHN0cnVjdHVyZXMgdGhhdCBjb250YWluIHNvbWUgZ3JvdXBpbmcgb2YgZ2VuZXMsIGUuZy4gcGF0aHdheXMgb3IgbG9jYXRpb25zLCBhbmQgYSBsaXN0IG9mIGdlbmVzIGZvciB0aGF0IGdyb3VwLiBQYXRod2F5cyBhcmUgZG93bmxvYWRlZCBmcm9tIFtnc2VhLW1zaWdkYl0oaHR0cHM6Ly93d3cuZ3NlYS1tc2lnZGIub3JnL2dzZWEvZG93bmxvYWRzLmpzcCkuIEZvciB0aGlzIG5vdGVib29rLCBJIHB1dCB0aGVtIG9uIGdkcml2ZSB0byB1cmwgcmVxdWVzdCwgYnV0IG90aGVyd2lzZSB0aGUgcGF0aHdheXMgc2hvdWxkIGJlIGRvd25sb2FkZWQgZnJvbSB0aGVyZS4gVGhlIGZpbGUgbmFtZSB3aWxsIGluY2x1ZGUgaG93IHRoZSBnZW5lcyBhcmUgaWRlbnRpZmllZCAtIGhlcmUgd2Ugd2lsbCB1c2UgJ3N5bWJvbHMsJyBzaW5jZSBTVEFSIGFsaWdubWVudCBnYXZlIHVzIGVuc2VtYmwgSURzLg0KDQpgYGB7ciBldmFsPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KZG93bmxvYWQuZmlsZSgiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL1NlcnZpY2VMb2dpbj9obD1lbiZwYXNzaXZlPXRydWUmY29udGludWU9aHR0cHM6Ly9kcml2ZS5nb29nbGUuY29tL3VjJTNGZXhwb3J0JTNEZG93bmxvYWQlMjZpZCUzRDEzM3RJOG9jSWgycTBNc1c1MWplckRlUEVuREg3MUdjcyZzZXJ2aWNlPXdyaXRlbHkiLA0KICAgICAgICAgICAgICBkZXN0ZmlsZSA9ICJjMi5jcC5rZWdnLnY3LjAuc3ltYm9scy5nbXQiKQ0KZG93bmxvYWQuZmlsZSgiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL1NlcnZpY2VMb2dpbj9obD1lbiZwYXNzaXZlPXRydWUmY29udGludWU9aHR0cHM6Ly9kcml2ZS5nb29nbGUuY29tL3VjJTNGZXhwb3J0JTNEZG93bmxvYWQlMjZpZCUzRDFXcFdJU1plVVVqUkk2QmxVS21VY0p1bW16VHh4VEU4eSZzZXJ2aWNlPXdyaXRlbHkiLA0KICAgICAgICAgICAgICBkZXN0ZmlsZSA9ICJjNS5hbGwudjcuMC5zeW1ib2xzLmdtdCIpDQpkb3dubG9hZC5maWxlKCJodHRwczovL2RyaXZlLmdvb2dsZS5jb20vdWM/ZXhwb3J0PWRvd25sb2FkJmlkPTFOZkkxakJKa05ST0NEd2RYZTkybnIzZDdjZmZVdy1meSIsDQogICAgICAgICAgICAgIGRlc3RmaWxlID0gImM1LmJwLnY3LjAuc3ltYm9scy5nbXQiKQ0KZG93bmxvYWQuZmlsZSgiaHR0cHM6Ly9kcml2ZS5nb29nbGUuY29tL3VjP2V4cG9ydD1kb3dubG9hZCZpZD0xV3lkY2tEeERnZWhuYWhDd0hzcUtocVVBZEhDcVJFNGgiLA0KICAgICAgICAgICAgICBkZXN0ZmlsZSA9ICJoLmFsbC52Ny4wLnN5bWJvbHMuZ210IikNCmBgYA0KDQpgYGB7cn0NCmZnc2VhX291dHB1dCA8LSBsaXN0KCkgICAgIyBUaGlzIHdpbGwgYmVjb21lIGEgZGF0YXN0cnVjdHVyZSB0aGF0IHdpbGwgaG9sZCBtdWx0aXBsZSBmR1NFQSByZXN1bHRzDQpmZ3NlYV9vdXRwdXRfcGxvdHMgPC0gbGlzdCgpDQpgYGANCg0KYGBge3J9DQpibSA8LSBnZXRCTShhdHRyaWJ1dGVzPWMoImVuc2VtYmxfZ2VuZV9pZCIsICJoc2FwaWVuc19ob21vbG9nX2Fzc29jaWF0ZWRfZ2VuZV9uYW1lIiksIG1hcnQ9ZW5zZW1ibE1vdXNlKSAlPiUNCiAgZGlzdGluY3QoKSAlPiUNCiAgYXNfdGliYmxlKCkgJT4lDQogIG5hX2lmKCIiKSAlPiUNCiAgbmEub21pdCgpDQpgYGANCg0KIyMjIFJ1biBmR1NFQQ0KDQpXZSdsbCBjb21wYXJlIHBhdGh3YXlzIGluIHRoZSBzYW1lIHdheSB3ZSBnZW5lcmF0ZWQgb3VyIGRpZmZlcmVudGlhbCBhbmFseXNpcyBjb21wYXJpc29ucywgZS5nLiB0aGUgNC13YXkgYW5hbHlzaXMgd2UgcmFuIHByZXZpb3VzbHkuIEluIG90aGVyIHdvcmRzLCB3ZSBjYW4gcHVsbCB0aGUgV2FsZCBzdGF0aXN0aWMgZnJvbSB0aGUgNCByZXN1bHRzIHdlIHJlY2lldmVkIGZyb20gREVTZXEgdG8gZ2VuZXJhdGUgcmFua2VkIGxpc3RzIGZvciBkaWZmZXJlbnQgcGF0aHdheSBhbmFseXNpcyBjb21wYXJpc29ucy4NCg0KV2UgYWxzbyBuZWVkIHRvIGNvbnNpZGVyIHRoZSBwYXRod2F5IGdlbmVzZXRzIHdlIHVzZS4gRm9yIFdUIFNQRiwgSSdsbCBydW4gcGF0aHdheSBhbmFseXNpcyB1c2luZyBIYWxsbWFyaywgS0VHRywgR2VuZSBPbnRvbG9neSB3aXRoIGFsbCB0YWdzLCBhbmQgR2VuZSBPbnRvbG9neSB3aXRoIG9ubHkgQmlvbG9naWNhbCBQcm9jZXNzZXNzIHRhZ3MuDQoNCkZvciB0aGUgc2FrZSBvZiB0aGUgdHV0b3JpYWwsIEknbGwgb25seSBydW4gYWxsIGRhdGFiYXNlcyBmb3IgYFdUX3Jlc2AuIEknbGwgcnVuIEtFR0cgYW5kIEdPIGZvciBgS09fcmVzYCwgdGhlbiBjb21wYXJlIHJlc3VsdHMgZm9yIFdUIGFuZCBLTyB0byBkZXRlcm1pbmUgd2hhdCBwYXRod2F5cyBhcmUgYmVpbmcgZXhwcmVzc2VkIGRlcGVuZGluZyBvbiB0aGUgZ2Vub3R5cGUgdGhhdCB2YXJ5IGJ5IG1pY3JvYmlhbCBzdGF0dXMsIG9yIGFyZSBpbmRlcGVuZGVudCBvZiBtaWNyb2JpYWwgc3RhdHVzLg0KDQpGaXJzdCBJIG5lZWQgdG8gZ2VuZXJhdGUgdGhlIHJhbmtlZCBsaXN0LiBJIGJ1aWxkIGByZXMyYCwgYSByYW5rZWQgZ2VuZSBsaXN0LCBiYXNlZCBvbiBgV1RfcmVzYC4gSSB3aWxsIHVzZSB0aGlzIHRvIHJ1biBmR1NFQSB1c2luZyBlYWNoIG9mIHRoZSBkaWZmZXJlbnQgcGF0aHdheXMuDQoNCmBgYHtyfQ0KcmVzMiA8LSBpbm5lcl9qb2luKFdUX3JlcywgYm0sIGJ5PWMoInJvdyI9ImVuc2VtYmxfZ2VuZV9pZCIpKSAlPiUNCiAgZHBseXI6OnNlbGVjdChoc2FwaWVuc19ob21vbG9nX2Fzc29jaWF0ZWRfZ2VuZV9uYW1lLCBzdGF0KSAlPiUNCiAgbmEub21pdCgpICU+JQ0KICBkaXN0aW5jdCgpICU+JQ0KICBncm91cF9ieShoc2FwaWVuc19ob21vbG9nX2Fzc29jaWF0ZWRfZ2VuZV9uYW1lKSAlPiUNCiAgc3VtbWFyaXplKHN0YXQ9bWVhbihzdGF0KSkNCmBgYA0KDQpUYWtlIGEgYnJpZWYgbG9vayBhdCB0aGUgcmFua3MgZ2VuZXJhdGVkIGZyb20gV1RfcmVzDQoNCmBgYHtyfQ0KcmFua3MgPC0gZGVmcmFtZShyZXMyKQ0KaGVhZChyYW5rcywgMjApDQpgYGANCg0KSSB1c2UgYGZnc2VhKClgIHdpdGggbnBlcm0gc2V0IHRvIDEwMDAwMCBiYXNlZCBvbiBbZWFybGllciB0ZXN0aW5nXShodHRwczovL3lveW9tYW56b29yLmdpdGh1Yi5pby9DaGFuZy1MYWItTm90ZWJvb2svbm90ZWJvb2svMjAxOS4xMi41Lmh0bWwpDQoNCmBgYHtyfQ0KZmdzZWFSZXNUaWR5IDwtIGZnc2VhKHBhdGh3YXlzPWdtdFBhdGh3YXlzKCJoLmFsbC52Ny4wLnN5bWJvbHMuZ210IiksIHN0YXRzPXJhbmtzLCBucGVybT0xMDAwMCkgJT4lDQogIGFzX3RpYmJsZSgpICU+JQ0KICBmaWx0ZXIocGFkaiA8IDAuMDUpICU+JQ0KICBhcnJhbmdlKGRlc2MoTkVTKSkNCmBgYA0KDQpSZXN1bHRzIGNhbiBiZSBzZWVuIGluIGEgdGFibGUNCg0KYGBge3J9DQpmZ3NlYVJlc1RpZHkgJT4lDQogIGRwbHlyOjpzZWxlY3QoLWxlYWRpbmdFZGdlLCAtRVMsIC1uTW9yZUV4dHJlbWUpICU+JQ0KICBhcnJhbmdlKHBhZGopDQpgYGANCg0KRmluYWxseSwgd2UgY2FuIHNlZSBob3cgcGF0aHdheXMgYXJlIGVucmljaGVkIG9yIG5vdCBlbnJpY2hlZCBpbiB0aGUgU1BGIGNvbmRpdGlvbiBjb21wYXJlZCB0byB0aGUgR0YgY29uZGl0aW9uIGZvciBXVCBtaWNlLg0KDQpgYGB7cn0NCmZnc2VhX291dHB1dF9wbG90cyRXVCRoYWxsbWFyayA8LSBnZ3Bsb3QoZmdzZWFSZXNUaWR5LCBhZXMocmVvcmRlcihwYXRod2F5LCBORVMpLCBORVMpKSArDQogIGdlb21fY29sKCkgKw0KICBjb29yZF9mbGlwKCkgKw0KICBsYWJzKHg9IlBhdGh3YXkiLCB5PSJOb3JtYWxpemVkIEVucmljaG1lbnQgU2NvcmUiLA0KICAgICAgIHRpdGxlPSJXVCBTUEYgdnMgR0Y6IEhhbGxtYXJrIHBhdGh3YXlzIE5FUyBmcm9tIEdTRUEiKQ0KDQpmZ3NlYV9vdXRwdXRfcGxvdHMkV1QkaGFsbG1hcmsNCmBgYA0KDQpXZSdsbCBnbyBhaGVhZCBhbmQgcnVuIHRoaXMgcGlwZSB3aXRoIHRoZSByZW1haW5pbmcgcGF0aHdheSBkYXRhYmFzZXMuIEkgYWRkIGFsbCB0aGUgZmdzZWEgcmVzdWx0cyB0byBhIGRhdGFzdHJ1Y3R1cmUgYGZnc2VhX291dHB1dGAgYW5kIHBsb3RzIHRvIGEgZGF0YXN0cnVjdHVyZSBgZmdzZWFfb3V0cHV0X3Bsb3RzYC4NCg0KYGBge3J9DQpmZ3NlYV9vdXRwdXQkV1QkS0VHRyA8LSBmZ3NlYShwYXRod2F5cz1nbXRQYXRod2F5cygiYzIuY3Aua2VnZy52Ny4wLnN5bWJvbHMuZ210IiksIHJhbmtzLCBucGVybT0xMDAwMCkgJT4lDQogIGFzX3RpYmJsZSgpICU+JQ0KICBmaWx0ZXIocGFkaiA8IDAuMDUpICU+JQ0KICBhcnJhbmdlKHBhZGopDQoNCmZnc2VhX291dHB1dF9wbG90cyRXVCRLRUdHIDwtIGdncGxvdChmZ3NlYV9vdXRwdXQkV1QkS0VHRywgYWVzKHJlb3JkZXIocGF0aHdheSwgTkVTKSwgTkVTKSkgKw0KICBnZW9tX2NvbCgpICsNCiAgY29vcmRfZmxpcCgpICsNCiAgbGFicyh4PSJQYXRod2F5IiwgeT0iTm9ybWFsaXplZCBFbnJpY2htZW50IFNjb3JlIiwNCiAgICAgICB0aXRsZT0iV1QgU1BGIHZzIEdGOiBLRUdHIHBhdGh3YXlzIE5FUyBmcm9tIEdTRUEiKQ0KDQpmZ3NlYV9vdXRwdXQkV1QkR09fQlAgPC0gZmdzZWEocGF0aHdheXM9Z210UGF0aHdheXMoImM1LmJwLnY3LjAuc3ltYm9scy5nbXQiKSwgcmFua3MsIG5wZXJtPTEwMDAwKSAlPiUNCiAgYXNfdGliYmxlKCkgJT4lDQogIGZpbHRlcihwYWRqIDwgMC4wNSkgJT4lDQogIGFycmFuZ2UocGFkaikNCg0KZmdzZWFfb3V0cHV0X3Bsb3RzJFdUJEdPX0JQIDwtIGdncGxvdChmZ3NlYV9vdXRwdXQkV1QkR09fQlAsIGFlcyhyZW9yZGVyKHBhdGh3YXksIE5FUyksIE5FUykpICsNCiAgZ2VvbV9jb2woKSArDQogIGNvb3JkX2ZsaXAoKSArDQogIGxhYnMoeD0iUGF0aHdheSIsIHk9Ik5vcm1hbGl6ZWQgRW5yaWNobWVudCBTY29yZSIsDQogICAgICAgdGl0bGU9IldUIFNQRiB2cyBHRjogR08gYmlvbG9naWNhbCBwcm9jZXNzZXMgcGF0aHdheXMgTkVTIGZyb20gR1NFQSIpDQpgYGANCg0KTm93IHRvIGNyZWF0ZSBhIGdlbmUgbGlzdCB1c2luZyBLTyBERVNlcSByZXN1bHRzDQoNCmBgYHtyfQ0KcmVzMiA8LSBpbm5lcl9qb2luKEtPX3JlcywgYm0sIGJ5PWMoInJvdyI9ImVuc2VtYmxfZ2VuZV9pZCIpKSAlPiUNCiAgZHBseXI6OnNlbGVjdChoc2FwaWVuc19ob21vbG9nX2Fzc29jaWF0ZWRfZ2VuZV9uYW1lLCBzdGF0KSAlPiUNCiAgbmEub21pdCgpICU+JQ0KICBkaXN0aW5jdCgpICU+JQ0KICBncm91cF9ieShoc2FwaWVuc19ob21vbG9nX2Fzc29jaWF0ZWRfZ2VuZV9uYW1lKSAlPiUNCiAgc3VtbWFyaXplKHN0YXQ9bWVhbihzdGF0KSkNCg0KcmFua3MgPC0gZGVmcmFtZShyZXMyKQ0KaGVhZChyYW5rcywgMjApDQpgYGANCg0KQW5kIHN1YnNlcXVlbnRseSBydW4gZkdTRUENCg0KYGBge3Igd2FybmluZz1GQUxTRX0NCmZnc2VhX291dHB1dCRLTyRLRUdHIDwtIGZnc2VhKHBhdGh3YXlzPWdtdFBhdGh3YXlzKCJjMi5jcC5rZWdnLnY3LjAuc3ltYm9scy5nbXQiKSwgcmFua3MsIG5wZXJtPTEwMDAwKSAlPiUNCiAgYXNfdGliYmxlKCkgJT4lDQogIGZpbHRlcihwYWRqIDwgMC4wNSkgJT4lDQogIGFycmFuZ2UocGFkaikNCg0KZmdzZWFfb3V0cHV0X3Bsb3RzJEtPJEtFR0cgPC0gZ2dwbG90KGZnc2VhX291dHB1dCRLTyRLRUdHLCBhZXMocmVvcmRlcihwYXRod2F5LCBORVMpLCBORVMpKSArDQogIGdlb21fY29sKCkgKw0KICBjb29yZF9mbGlwKCkgKw0KICBsYWJzKHg9IlBhdGh3YXkiLCB5PSJOb3JtYWxpemVkIEVucmljaG1lbnQgU2NvcmUiLA0KICAgICAgIHRpdGxlPSJLTyBTUEYgdnMgR0Y6IEtlZ2cgcGF0aHdheXMgTkVTIGZyb20gR1NFQSIpDQoNCmZnc2VhX291dHB1dCRLTyRHT19CUCA8LSBmZ3NlYShwYXRod2F5cz1nbXRQYXRod2F5cygiYzUuYnAudjcuMC5zeW1ib2xzLmdtdCIpLCByYW5rcywgbnBlcm09MTAwMDApICU+JQ0KICBhc190aWJibGUoKSAlPiUNCiAgZmlsdGVyKHBhZGogPCAwLjA1KSAlPiUNCiAgYXJyYW5nZShwYWRqKQ0KDQpmZ3NlYV9vdXRwdXRfcGxvdHMkS08kR09fQlAgPC0gZ2dwbG90KGZnc2VhX291dHB1dCRLTyRHT19CUCwgYWVzKHJlb3JkZXIocGF0aHdheSwgTkVTKSwgTkVTKSkgKw0KICBnZW9tX2NvbCgpICsNCiAgY29vcmRfZmxpcCgpICsNCiAgbGFicyh4PSJQYXRod2F5IiwgeT0iTm9ybWFsaXplZCBFbnJpY2htZW50IFNjb3JlIiwNCiAgICAgICB0aXRsZT0iS08gU1BGIHZzIEdGOiBHTyBiaW9sb2dpY2FsIHByb2Nlc3NlcyBwYXRod2F5cyBORVMgZnJvbSBHU0VBIikNCmBgYA0KDQpgYGB7ciwgb3V0LndpZHRoPTIzLCBvdXQuaGVpZ2h0PTEyLCBldmFsPUZBTFNFfQ0KZjEgPC0gZ2dhcnJhbmdlKGZnc2VhX291dHB1dF9wbG90cyRXVCRLRUdHLCBmZ3NlYV9vdXRwdXRfcGxvdHMkS08kS0VHRywNCiAgICAgICAgICBsYWJlbHMgPSBjKCJXVCBLRUdHIiwgIktPIEtFR0ciKSkNCmFubm90YXRlX2ZpZ3VyZSgNCiAgZjEsDQogIHRvcCA9IHRleHRfZ3JvYigiXG5XVCBhbmQgS08gU1BGIHZzIEdGIEtFR0cgcGF0aHdheXNcbiIsDQogICAgICAgICAgICAgICAgICBjb2xvciA9ICJyZWQiLCBmYWNlID0gImJvbGQiLCBzaXplID0gMjApDQopDQpgYGANCg0KIVtdKGltYWdlcy9mMV8xLnBuZykNCg0KQSBwb3NpdGl2ZSBub3JtYWxpemVkIGVucmljaG1lbnQgc2NvcmUgKE5FUykgbWVhbnMgbW9yZSBnZW5lcyBleHByZXNzZWQgZm9yIHRoaXMgcGF0aHdheSBpbiBTUEYsIGFuZCB0aGUgcGF0aHdheSBpcyBlbnJpY2hlZCBpbiBTUEYsIGEgbmVnYXRpdmUgc2NvcmUgbWVhbnMgbW9yZSBnZW5lcyBleHByZXNzZWQgZm9yIHRoaXMgcGF0aHdheSBpbiBHRi4NCg0KDQpXZSBjYW4gc2VlIHdoYXQgcGF0aHdheXMgc2hvd2VkIHVwIGluIGJvdGggV1QgYW5kIEtPIGFuYWx5c2VzIGFib3ZlLiBJJ2xsIGNoZWNrIEtFR0cgYW5kIEdPIGRhdGFiYXNlcyBhbmQgc2hvdyByZXN1bHRzIG9ubHkgZm9yIHBhdGh3YXlzIGluIGJvdGggV1Qgb3IgS08gY29uZGl0aW9ucy4NCg0KVGhpcyBtZXRob2Qgb2YgY29tYmluaW5nIHJlc3VsdHMgY2FuIGJlIGV4cGFuZGVkIHVzaW5nIGJhc2ljIHNldCB0aGVvcnkuIEkgYW0gb25seSBkb2luZyBhbiBpbnRlcnNlY3Rpb24gaW4gdGhpcyBleGNlcnNpemUuDQoNCmBgYHtyLCBvdXQuaGVpZ2h0PTE1LCBvdXQuaGVpZ2h0PTEyLCBldmFsPUZBTFNFfQ0KZmdzZWFfb3V0cHV0JFdUX0tPJEtFR0cgPC0gaW5uZXJfam9pbihmZ3NlYV9vdXRwdXQkV1QkS0VHRywgZmdzZWFfb3V0cHV0JEtPJEtFR0csIGJ5ID0gInBhdGh3YXkiLCBzdWZmaXggPSBjKCIuV1QiLCAiLktPIikpDQoNCmZnc2VhX291dHB1dF9wbG90cyRXVF9LTyRLRUdHIDwtIGRhdGEuZnJhbWUocGF0aHdheSA9IGZnc2VhX291dHB1dCRXVF9LTyRLRUdHJHBhdGh3YXksICJXVCBORVMiID0gZmdzZWFfb3V0cHV0JFdUX0tPJEtFR0ckTkVTLldULCAiS08gTkVTIiA9IGZnc2VhX291dHB1dCRXVF9LTyRLRUdHJE5FUy5LTykgJT4lDQogIHJlc2hhcGUyOjptZWx0KGlkPSJwYXRod2F5IikgJT4lDQogIGdncGxvdChhZXMoeCA9IHJlb3JkZXIocGF0aHdheSwgLXZhbHVlKSwgeSA9IHZhbHVlLCBmaWxsID0gdmFyaWFibGUpKSArDQogICAgZ2VvbV9jb2woKSArDQogICAgY29vcmRfZmxpcCgpICsNCiAgICBmYWNldF93cmFwKH4gdmFyaWFibGUsIGxhYmVsbGVyID0gbGFiZWxsZXIodmFyaWFibGUgPSBjKCJXVC5ORVMiID0gIldUIFNQRiB2cyBHRiIsICJLTy5ORVMiID0gIktPIFNQRiB2cyBHRiIpKSkgKw0KICAgIHhsYWIoIlBhdGh3YXkiKSArDQogICAgeWxhYigiTm9ybWFsaXplZCBFbnJpY2htZW50IFNjb3JlIikgKw0KICAgIGdndGl0bGUoIlxuU1BGIHZzIEdGIHBhdGh3YXkgZW5yaWNobWVudFxuIikgKw0KICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMjAsIGNvbG9yID0gInJlZCIsIGZhY2UgPSAiYm9sZCIpKQ0KDQpmZ3NlYV9vdXRwdXRfcGxvdHMkV1RfS08kS0VHRw0KYGBgDQoNCiFbXShpbWFnZXMvZjJfMS5wbmcpDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KZmdzZWFfb3V0cHV0JFdUX0tPJEdPX0JQIDwtIGlubmVyX2pvaW4oZmdzZWFfb3V0cHV0JFdUJEdPX0JQLCBmZ3NlYV9vdXRwdXQkS08kR09fQlAsIGJ5ID0gInBhdGh3YXkiLCBzdWZmaXggPSBjKCIuV1QiLCAiLktPIikpDQoNCmZnc2VhX291dHB1dF9wbG90cyRXVF9LTyRHT19CUCA8LSBkYXRhLmZyYW1lKHBhdGh3YXkgPSBmZ3NlYV9vdXRwdXQkV1RfS08kR09fQlAkcGF0aHdheSwgIldUIE5FUyIgPSBmZ3NlYV9vdXRwdXQkV1RfS08kR09fQlAkTkVTLldULCAiS08gTkVTIiA9IGZnc2VhX291dHB1dCRXVF9LTyRHT19CUCRORVMuS08pICU+JQ0KICByZXNoYXBlMjo6bWVsdChpZD0icGF0aHdheSIpICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSByZW9yZGVyKHBhdGh3YXksIC12YWx1ZSksIHkgPSB2YWx1ZSwgZmlsbCA9IHZhcmlhYmxlKSkgKw0KICBnZW9tX2NvbCgpICsNCiAgY29vcmRfZmxpcCgpICsNCiAgZmFjZXRfd3JhcCh+IHZhcmlhYmxlLCBsYWJlbGxlciA9IGxhYmVsbGVyKHZhcmlhYmxlID0gYygiV1QuTkVTIiA9ICJXVCBTUEYgdnMgR0YiLCAiS08uTkVTIiA9ICJLTyBTUEYgdnMgR0YiKSkpICsNCiAgeGxhYigiUGF0aHdheSIpICsNCiAgeWxhYigiTm9ybWFsaXplZCBFbnJpY2htZW50IFNjb3JlIikgKw0KICBnZ3RpdGxlKCJcblNQRiB2cyBHRiBHTyBCUCBwYXRod2F5IGVucmljaG1lbnRcbiIpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLCBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAyMCwgY29sb3IgPSAicmVkIiwgZmFjZSA9ICJib2xkIikpDQoNCmZnc2VhX291dHB1dF9wbG90cyRXVF9LTyRHT19CUA0KYGBgDQoNCiFbXShpbWFnZXMvZjNfMS5wbmcpDQoNClRodXMsIHZhcnlpbmcgbWljcm9iaWFsIGNvbmRpdGlvbiBiZXR3ZWVuIFdUIGFuZCBMLUJtYWwxLUtPIGF0IFpUMiBkaWQgbm90IGxlYWQgdG8gZGlmZmVyZW50IHBhdGh3YXkgZXhwcmVzc2lvbiBpbiB0aGUgbWljZS4NCg0KRnVydGhlciBhbmFseXNpcyBpbmNsdWRlcyBsb29raW5nIGF0IGhvdyB2YXJ5aW5nIGdlbm90eXBlIGxlYWRzIHRvIGRpZmZlcmVuY2VzIGJldHdlZW4gU1BGIGFuZCBHRiBjb25kaXRpb25zIGF0IG11bHRpcGxlIHRpbWVwb2ludHMuDQoNClRoaXMgY29tcGxldGVzIHRoZSB0dXRvcmlhbCBmb3IgcnVubmluZyBkaWZmZXJlbnRpYWwgYW5hbHlzaXMgZm9sbG93ZWQgYnkgcGF0aHdheSBhbmFseXNpcyB1c2luZyBERVNlcTIgYW5kIGZHU0VBLg0K
 

By Sumeed Yoyo Manzoor

smanzoor@uchicago.edu